package org.masterview.rebind;

import com.google.gwt.core.ext.typeinfo.*;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;

import java.io.PrintWriter;

public class MasterViewGenerator extends Generator {
    public String generate(TreeLogger logger, GeneratorContext context,
                           String typeName) throws UnableToCompleteException {
        try {
            TypeOracle typeOracle = context.getTypeOracle();
            JClassType requestedClass = typeOracle.getType(typeName);

            String packageName = requestedClass.getPackage().getName();
            String simpleClassName = requestedClass.getSimpleSourceName();
            String proxyClassName = simpleClassName + "MasterView";
            String qualifiedProxyClassName = packageName + "." + proxyClassName;
            String superclassName = "org.masterview.user.client.ui.MasterView";

            SourceWriter writer = getSourceWriter(logger, context, requestedClass, packageName,
                    proxyClassName, superclassName);
            logger.log(TreeLogger.INFO, "Starting rewriting using: " +
                    this.getClass().getSimpleName() + " for " + " version", null);

            if (writer == null) {
                logger.log(TreeLogger.INFO, "Cancelled rewriting - class alredy exists", null);
                return qualifiedProxyClassName;
            } else {
                writeCode(logger, requestedClass, proxyClassName, writer);

                writer.commit(logger);
                logger.log(TreeLogger.INFO, "Completed rewriting", null);

                return qualifiedProxyClassName;
            }
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Class '" + typeName + "' Not Found", e);
            throw new UnableToCompleteException();
        }
    }

    private void writeCode(TreeLogger logger, JClassType requestedClass,
                           String simpleClassName, SourceWriter writer) {
        StringBuilder codeBuilder = new StringBuilder();

        writer.println("public PropertyMapper getPropertyMapper() {");

        writer.indent();
        writer.println("return new PropertyMapper() {");

        writer.indent();
        writer.indent();
        writer.println("public Object getProperty(Object object, String propertyName) {");

        JMethod[] methods = requestedClass.getMethods();
        JField[] fields = requestedClass.getFields();

        writer.indent();
        writer.indent();
        writer.indent();
        writer.println(requestedClass.getSimpleSourceName() + " bean = (" +
                requestedClass.getSimpleSourceName() + ") object;");

        for (int i = 0; i < fields.length; i++) {
            JField field = fields[i];

            String fieldName = field.getName();
            String firstLetterCapitalized = fieldName.substring(0, 1).toUpperCase();
            String fieldWithoutFirstLetter = fieldName.substring(1, fieldName.length());
            String accessorName = "get" + firstLetterCapitalized + fieldWithoutFirstLetter;

            if (hasSuitableAccessor(methods, accessorName)) {
                logger.log(TreeLogger.INFO, "processing " + fieldName + " field", null);
                writer.indent();
                writer.indent();
                writer.indent();
                writer.println("if (propertyName == \"" + field.getName() + "\") {");

                writer.indent();
                writer.indent();
                writer.indent();
                writer.indent();

                if (field.getType().getSimpleSourceName().equals("int")) {
                    writer.println("return new Integer(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("boolean")) {
                    writer.println("return new Boolean(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("char")) {
                    writer.println("return new Char(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("byte")) {
                    writer.println("return new Byte(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("short")) {
                    writer.println("return new Short(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("long")) {
                    writer.println("return new Long(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("float")) {
                    writer.println("return new Float(bean." + accessorName + "());");
                } else if (field.getType().getSimpleSourceName().equals("double")) {
                    writer.println("return new Double(bean." + accessorName + "());");
                } else {
                    writer.println("return bean." + accessorName + "();");
                }

                writer.indent();
                writer.indent();
                writer.indent();
                writer.println("}");
            }
        }

        writer.indent();
        writer.indent();
        writer.indent();
        writer.println("return null;");

        writer.indent();
        writer.indent();
        writer.println("}");

        writer.indent();
        writer.println("};");

        writer.println("}");

        logger.log(TreeLogger.DEBUG, codeBuilder.toString(), null);
        writer.println(codeBuilder.toString());
    }

    private boolean hasSuitableAccessor(JMethod[] methods, String accessorName) {

        for (int j = 0; j < methods.length; j++) {
            JMethod method = methods[j];

            if (method.getName().equals(accessorName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns a SourceWriter object for the new class to be built
     *
     * @param logger
     * @param context
     * @param packageName
     * @param className
     * @param superclassName
     * @return null if the new class name already exists in the set of
     *         classes the GWT compiler will work on, or a SourceWriter object
     *         representing the new class otherwise.
     */
    protected SourceWriter getSourceWriter(TreeLogger logger,
                                           GeneratorContext context,
                                           JClassType requestedClass,
                                           String packageName,
                                           String className,
                                           String superclassName) {
        PrintWriter printWriter = context.tryCreate(logger, packageName, className);

        if (printWriter == null) return null;

        ClassSourceFileComposerFactory composerFactory =
                new ClassSourceFileComposerFactory(packageName, className);

        logger.log(TreeLogger.INFO, "adding import for " + requestedClass.getQualifiedSourceName() +
                " class", null);
        composerFactory.addImport("java.util.List");
        composerFactory.addImport(requestedClass.getQualifiedSourceName());
        composerFactory.addImport("org.masterview.user.client.data.PropertyMapper");

        logger.log(TreeLogger.INFO, "setting superclass = " + superclassName, null);
        composerFactory.setSuperclass(superclassName);

        return composerFactory.createSourceWriter(context, printWriter);
    }
}
